#include "sound/pulse_audio_io.h"
#include "logger.h"
#include <iostream>
#include <pulse/error.h>
#include <pulse/channelmap.h>

#define MAX_FRAMES 4096
#define MAX_CAPTURE_FRAMES 160
#define PULSE_DEFAULT_SAMPLE_RATE 8000

using namespace std;

const std::string CPulseAudioIO::sIniSectionName = "PULSE_IO";
const std::string CPulseAudioIO::sConfPlaybackSink = "PLAYBACK_SINK";
const std::string CPulseAudioIO::sConfCaptureSource = "CAPTURE_SOURCE";

CPulseAudioIO::CPulseAudioIO(EAction pAction, unsigned int pChannels) :
		mError(INIT_FAILURE), mAction(pAction), mPlaybackChannels(0), mPlaybackHandle(NULL), mCaptureChannels(pChannels), mCaptureHandle(NULL), mCaptureBuffer(NULL), mBytePerSample(0), mDoPlayback(false), mDoCapture(false), mCaptureSource(
				NULL), mPlaybackSink(NULL) {
	mStreamInfo.mSamplesWidth = NAudioFormats::S16LE;
	mStreamInfo.mSampleRate = PULSE_DEFAULT_SAMPLE_RATE;
	mStreamInfo.mCodec = NAudioFormats::PCM;

	mBytePerSample = getBytePerSample();

	mDoCapture = true;
	mError = INIT_NO_ERROR;
}

// for playback
CPulseAudioIO::CPulseAudioIO(EAction pAction, unsigned int pChannels, NAudioFormats::SStreamInfo pStreamInfo) :
		mError(INIT_FAILURE), mAction(pAction), mPlaybackChannels(pChannels), mPlaybackHandle(NULL), mCaptureChannels(0), mCaptureHandle(NULL), mStreamInfo(pStreamInfo), mBytePerSample(0), mDoPlayback(false), mDoCapture(false), mCaptureSource(
				NULL), mPlaybackSink(NULL) {
	mBytePerSample = getBytePerSample();

	mDoCapture = true;
	mError = INIT_NO_ERROR;
}

CPulseAudioIO::CPulseAudioIO(EAction pAction) :
		mError(INIT_FAILURE), mAction(pAction), mPlaybackChannels(0), mPlaybackHandle(NULL), mCaptureChannels(0), mCaptureHandle(NULL), mBytePerSample(0), mDoPlayback(false), mDoCapture(false), mCaptureSource(NULL), mPlaybackSink(NULL) {
	switch (mAction) {
	case UNKNOWN_ACTION:
	case NO_ACTION:
		return;
	case EMBED:
	case EXTRACT:
		mPlaybackChannels = 1;
		mCaptureChannels = 1;
		break;
	case INTERLEAVED_EMBED_EXTRACT:
		mPlaybackChannels = 2;
		mCaptureChannels = 2;
		break;
	}
	mStreamInfo.mSamplesWidth = NAudioFormats::S16LE;
	mStreamInfo.mSampleRate = PULSE_DEFAULT_SAMPLE_RATE;
	mStreamInfo.mCodec = NAudioFormats::PCM;

	mBytePerSample = getBytePerSample();

	mDoPlayback = true;
	mDoCapture = true;
	mError = INIT_NO_ERROR;
}

CPulseAudioIO::~CPulseAudioIO() {
	if (mDoCapture&&mCaptureHandle) {
		pa_simple_free(mCaptureHandle);
		mCaptureHandle = NULL;
	}
	if (mDoPlayback&&mPlaybackHandle) {
		pa_simple_free(mPlaybackHandle);
		mPlaybackHandle = NULL;
	}
}

int CPulseAudioIO::init(CPropertiesReader *pConfig) {

	if (mError != INIT_NO_ERROR) {
		return -1;
	}

	// read pulse settings
	std::string lString;

	if (pConfig->containsKey(sIniSectionName, sConfCaptureSource)) {
		if (pConfig->getValue(sIniSectionName, sConfCaptureSource, lString)) {
			printl(0, __FILE__, "%s.%s: %s",sIniSectionName.c_str(),sConfCaptureSource.c_str(),lString.c_str());
			if(lString != "NULL"){
				mCaptureSource=lString.c_str();
				printl(0, __FILE__, "using capture source %s",mCaptureSource);
			}else {
				printl(0, __FILE__, "using capture source default");
			}
		}
	}


	if (pConfig->containsKey(sIniSectionName, sConfPlaybackSink)) {
			if (pConfig->getValue(sIniSectionName, sConfPlaybackSink, lString)) {
				printl(0, __FILE__, "%s.%s: %s",sIniSectionName.c_str(),sConfPlaybackSink.c_str(),lString.c_str());
				if(lString != "NULL"){
					mPlaybackSink=lString.c_str();
					printl(0, __FILE__, "using playback sink %s",mPlaybackSink);
				}else {
					printl(0, __FILE__, "using playback sink default");
				}
			}
		}

	if (mDoPlayback&&!initPlayback()) {
		return -1;
	}
	if (mDoCapture&&!initCapture()) {
		return -1;
	}

	return 0;
}

int CPulseAudioIO::destroy() {
	int lErr;
	if (mCaptureHandle) {
		printl(0, __FILE__, "Stop capture.\n");
		if (pa_simple_drain(mCaptureHandle, &lErr) < 0) {
			printl(LL_ERR, __FILE__, "pa_simple_drain for capture failed. (%s)\n", pa_strerror(lErr));
		}
	}
	if (mPlaybackHandle) {
		//printl(0, __FILE__, "sleep 30.\n");
		//sleep(60);
		printl(0, __FILE__, "Wait for all pending frames to be played ...\n");
		// For playback wait for all pending frames to be played and then stop
		// the PCM. For capture stop PCM permitting to retrieve residual frames.
		if (pa_simple_drain(mPlaybackHandle, &lErr) < 0) {
			printl(LL_ERR, __FILE__, "pa_simple_drain for playback failed. (%s)\n", pa_strerror(lErr));
		}
		printl(0, __FILE__, "Now playback will be stopped.\n");
	}
	return 0;
}

int CPulseAudioIO::read(void** pData, EAction& pAction, int pTimeout) {
	if (mError != INIT_NO_ERROR) {
		return -1;
	}
	if (!mCaptureBuffer || !mCaptureChannels || !mCaptureHandle || !mBytePerSample) {
		return -1;
	}

	int lLength(MAX_CAPTURE_FRAMES * mBytePerSample * mCaptureChannels);
	int lErr(0);
	if (pa_simple_read(mCaptureHandle, mCaptureBuffer, lLength, &lErr) < 0) {
		printl(LL_ERR, __FILE__, "pa_simple_read failed. (%s)\n", pa_strerror(lErr));
	}

	*pData = mCaptureBuffer;
	pAction = mAction;
	return lLength;
}

int CPulseAudioIO::write(void* pData, const EAction& pAction, int pLength) {
	if (mError != INIT_NO_ERROR) {
		return -1;
	}
	if (!mPlaybackChannels || !mPlaybackHandle || !mBytePerSample) {
		return -1;
	}
	if ((pLength % mBytePerSample) != 0) {
		printl(LL_ERR, __FILE__, "write: data length is not a multiple of the sample size.\n");
		return -1;
	}
	if (((pLength / mBytePerSample) % mPlaybackChannels) != 0) {
		printl(LL_ERR, __FILE__, "write: sample length is not a multiple of the channel count.\n");
		return -1;
	}

	int lErr(0);
	if (pa_simple_write(mPlaybackHandle, pData, pLength, &lErr) < 0) {
		printl(LL_ERR, __FILE__, "pa_simple_write failed. (%s)\n", pa_strerror(lErr));
		return -1;
	}
	return pLength;
}

NAudioFormats::SStreamInfo CPulseAudioIO::getStreamInfo() {
	return mStreamInfo;
}

bool CPulseAudioIO::initPlayback() {
	int lError(0);
	pa_sample_spec lSampleSpec = { PA_SAMPLE_INVALID, 0, 0 };
	if (!getPASampleSpecsFromStreamInfo(lSampleSpec, false)) {
		return false;
	}

	pa_channel_map lChannelMap;
	switch (mPlaybackChannels) {
	case 1:
		lChannelMap.channels = 1;
		lChannelMap.map[0] = PA_CHANNEL_POSITION_MONO;
		break;
	case 2:
		lChannelMap.channels = 2;
		lChannelMap.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
		lChannelMap.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
		break;
	default:
		return false;
	}

	/* Create a new playback stream */
	mPlaybackHandle = pa_simple_new(NULL, // Server name, or NULL for default
			"StegIT-2", // A descriptive name for this client (application name)
			PA_STREAM_PLAYBACK, // 	Open this stream for recording or playback
			mPlaybackSink, // Sink (resp. source) name, or NULL for default
			"playback", // A descriptive name (application name, song title, ...)
			&lSampleSpec, // The sample type to use
			&lChannelMap, // The channel map to use, or NULL for default
			NULL, // Buffering attributes, or NULL for default
			&lError);
	if (!mPlaybackHandle) {
		printl(0, __FILE__, "pa_simple_new() failed: %s\n", pa_strerror(lError));
		return false;
	}
	return true;
}

bool CPulseAudioIO::initCapture() {
	int lError(0);
	pa_sample_spec lSampleSpec = { PA_SAMPLE_INVALID, 0, 0 };
	if (!getPASampleSpecsFromStreamInfo(lSampleSpec, true)) {
		return false;
	}

	pa_channel_map lChannelMap;
	switch (mCaptureChannels) {
	case 1:
		lChannelMap.channels = 1;
		lChannelMap.map[0] = PA_CHANNEL_POSITION_MONO;
		break;
	case 2:
		lChannelMap.channels = 2;
		lChannelMap.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
		lChannelMap.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
		break;
	default:
		return false;
	}

	/* Create a new Capture stream */
	mCaptureHandle = pa_simple_new(NULL, // Server name, or NULL for default
			"StegIT-2", // A descriptive name for this client (application name)
			PA_STREAM_RECORD, // 	Open this stream for recording or playback
			mCaptureSource, // Sink (resp. source) name, or NULL for default
			"record", // A descriptive name (application name, song title, ...)
			&lSampleSpec, // The sample type to use
			&lChannelMap, // The channel map to use, or NULL for default
			NULL, // Buffering attributes, or NULL for default
			&lError);
	if (!mCaptureHandle) {
		printl(LL_ERR, __FILE__, "pa_simple_new() failed: %s\n", pa_strerror(lError));
		return false;
	}

	mCaptureBuffer = new char[MAX_CAPTURE_FRAMES * mBytePerSample * mCaptureChannels];
	printl(0, __FILE__, "Create successful a new pulse audio capture handle.\n");
	return true;
}

unsigned int CPulseAudioIO::getBytePerSample() {
	switch (mStreamInfo.mSamplesWidth) {
	case NAudioFormats::UNKNOWN_WIDTH:
		printl(LL_ERR, __FILE__, "Unknown sample width.\n");
		return 0;
	case NAudioFormats::S8:
	case NAudioFormats::U8:
		return 1;
	case NAudioFormats::S16LE:
	case NAudioFormats::S16BE:
	case NAudioFormats::S16HO:
	case NAudioFormats::U16LE:
	case NAudioFormats::U16BE:
	case NAudioFormats::U16HO:
		return 2;
	case NAudioFormats::DOUBLE:
		printl(LL_WARN, __FILE__, "Sample width for double is used.\n");
		return (sizeof(double) + 7) / 8;
	}
	printl(LL_ERR, __FILE__, "Unsupported sample width.\n");
	return 0;
}

bool CPulseAudioIO::getPASampleSpecsFromStreamInfo(pa_sample_spec &pSampleSpec, bool pForCapture) {
	switch (mStreamInfo.mCodec) {
	case NAudioFormats::UNKNOWN_CODEC:
		printl(LL_ERR, __FILE__, "Unknown codec.\n");
		return false;
	case NAudioFormats::PCM:
		switch (mStreamInfo.mSamplesWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_ERR, __FILE__, "Unknown sample width for PCM.\n");
			return false;

		case NAudioFormats::S8:
			printl(LL_ERR, __FILE__, "S8 isn't supported by pulse audio (no PA_SAMPLE_S8).\n");
			return false;
		case NAudioFormats::U8:
			pSampleSpec.format = PA_SAMPLE_U8;
			break;

		case NAudioFormats::S16LE:
			pSampleSpec.format = PA_SAMPLE_S16LE;
			break;
		case NAudioFormats::S16BE:
			pSampleSpec.format = PA_SAMPLE_S16BE;
			break;
		case NAudioFormats::S16HO:
#					if __BYTE_ORDER == __LITTLE_ENDIAN
			pSampleSpec.format = PA_SAMPLE_S16LE;
#					elif __BYTE_ORDER == __BIG_ENDIAN
			pSampleSpec.format = PA_SAMPLE_S16BE;
#					else
			printl(LL_ERR, __FILE__, "Can't define endianness\n");
			return false;
#					endif
			break;

		case NAudioFormats::U16LE:
		case NAudioFormats::U16BE:
		case NAudioFormats::U16HO:
			printl(LL_ERR, __FILE__, "U16 isn't supported by pulse audio.\n");
			break;

		case NAudioFormats::DOUBLE:
			printl(LL_ERR, __FILE__, "Double isn't supported by pulse audio.\n");
			return false;
		}
		break;
	case NAudioFormats::ALAW:
		switch (mStreamInfo.mSamplesWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_ERR, __FILE__, "Unknown sample width for ALAW.\n");
			return false;
		case NAudioFormats::S8:
		case NAudioFormats::U8:
			pSampleSpec.format = PA_SAMPLE_ALAW;
			break;
		default:
			printl(LL_ERR, __FILE__, "Unsupported sample width for ALAW.\n");
			return false;
		}
		break;
	case NAudioFormats::ULAW:
		switch (mStreamInfo.mSamplesWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_ERR, __FILE__, "Unknown sample width for ULAW.\n");
			return false;
		case NAudioFormats::S8:
		case NAudioFormats::U8:
			pSampleSpec.format = PA_SAMPLE_ULAW;
			break;
		default:
			printl(LL_ERR, __FILE__, "Unsupported sample width for ULAW.\n");
			return false;
		}
		break;
	}

	if (mStreamInfo.mSampleRate == 0) {
		printl(LL_ERR, __FILE__, "Sample rate can't be 0.\n");
		return false;
	}
	pSampleSpec.rate = mStreamInfo.mSampleRate;

	if (pForCapture) {
		pSampleSpec.channels = mCaptureChannels;
	} else {
		pSampleSpec.channels = mPlaybackChannels;
	}
	if (pSampleSpec.channels == 0) {
		printl(LL_ERR, __FILE__, "Channel count can't be 0.\n");
		return false;
	}

	return true;
}
